package dungeonPackage;

import static org.junit.Assert.*;

import java.util.*;

import org.junit.*;

import squareBorderPackage.*;

/**
 * A class collecting tests for the class of composite dungeons.
 * 
 * @author Jessa Bekker & Esther Alberts
 * @version 1.0
 */
public class CompositeDungeonTest {
	
	/**
	 * Class variables referencing CompositeDungeons, SingularDungeons and normal squares that do not change during the
	 * entire test case.
	 */
	private static Orientation defaultOrientation, orientation1, orientation1plus,  levelOrientation,
		rootOrientation, motherOrientation, shaftOrientation, motherReference1, motherReference2, O210,
		shaftNextMotherOr, levelInMotherOr, shaftNextMotherRef, levelInMotherRef;
	
	/**
	 * Class variables referencing CompositeDungeons that do change during the
	 * entire test case.
	 */
	private static CompositeDungeon<AccessibleSquare> dungeon;
	private static CompositeDungeon<AccessibleSquare> rootDungeon, motherDungeon1, motherDungeon2;
	private static SingularDungeon<AccessibleSquare> levelInMother, level2, level, shaft, shaftNextMother;	
	private CompositeDungeon<AccessibleSquare> dungeonMutable;	
	private NormalSquare square1, square2, square3, square4, square5, square6, square7;
	
	
	/**
	 * Set up an immutable test fixture.
	 * 
	 * @post	The variable defaultOrientation references a new Orientation with coordinates (30,20,0)
	 * @post	The variable orientation1 references a new Orientation with coordinates (20,0,30)
	 * @post	The variable orientation1plus references a new Orientation with coordinates (20,0,31)
	 * @post	The variable rootOrientation references a new Orientation with coordinates (1000,1000,1000)
	 * @post	The variable motherOrientation references a new Orientation with coordinates (100,100,100)
	 * @post	The variable levelOrientation references a new Orientation with coordinates (10,10,0)
	 * @post	The variable shaftOrientation references a new Orientation with coordinates (0,0,5)
	 * @post	The variable motherReference1 references a new Orientation with coordinates (500,500,500)
	 * @post	The variable motherReference2 references a new Orientation with coordinates (0,10,20)
	 * @post	The variable shaftNextMotherOr references a new Orientation with coordinates (0,100,0)
	 * @post	The variable shaftNextMotherRef references a new Orientation with coordinates (499,500,500)
	 * @post	The variable levelInMotherOr references a new Orientation with coordinates (100,100,0)	 
	 * @post	The variable levelInMotherRef references a new orientation with coordinates (0,0,0)
	 * @post	The variable Q210 references a new Orientation with coordinates (2,1,0)	 
	 */
	@BeforeClass
	public static void setUpBeforeClass() {
		defaultOrientation = new Orientation(30, 20, 0);
		orientation1 = new Orientation(20, 0, 30);
		orientation1plus = new Orientation(20, 0, 31);
		rootOrientation = new Orientation(1000, 1000, 1000);
		motherOrientation = new Orientation(100, 100, 100);
		levelOrientation = new Orientation(10, 10, 0);
		shaftOrientation = new Orientation(0, 0, 5);
		motherReference1 = new Orientation(500, 500, 500);
		motherReference2 = new Orientation(0, 10, 20);
		shaftNextMotherOr = new Orientation(0,100,0);
		shaftNextMotherRef = new Orientation(499,500,500);
		levelInMotherOr = new Orientation(100,100,0);
		levelInMotherRef = new Orientation(0,0,0);
		O210 = new Orientation(2,1,0);
	}

	/**
	 * set up a mutable test fixture.
	 * 
	 * @post	The variables square1, square2, square3, square4, square5, square6 and square7 reference 
	 * 			a new NormalSquare with a temperature of 25 degree celsius and humidity of ten percent.
	 * @post	The variable dungeon references a new CompositeDungeon with maxCoordinates defaultOrientation
	 * @post	The variable dungeonMutable references a new CompositeDungeon with maxCoordinates motherOrientation	 
	 * @post	The variable rootDungeon references a new CompositeDungeon with maxCoordinates rootOrientation
	 * @post	The variable motherDungeon1 references a new CompositeDungeon with maxCoordinates motherOrientation
	 * @post	The variable motherDungeon2 references a new CompositeDungeon with maxCoordinates motherOrientation
	 * @post	The variable defaultDungeon references a new Level with maxCoordinates levelOrientation
	 * @post	The variable defaultDungeon references a new Shaft with maxCoordinates shaftOrientation
	 * @post	The variable defaultDungeon references a new Level with maxCoordinates shaftNextMotherOr
	 * @post	The variable defaultDungeon references a new Level with maxCoordinates levelInMotherOr
	 * @post	The variable defaultDungeon references a new Level with maxCoordinates defaultOrientation
	 * @post	motherDungeon1 is inserted in rootdungeon at the orientation motherReference1.
	 * @post	motherDungeon2 is inserted in rootdungeon at the orientation motherReference2.
	 * @post	shaftNextMother is inserted in rootdungeon at the orientation shaftNextMotherRef.
	 * @post	level is inserted in motherdungeon1 at the orientation orientation1.
	 * @post	levelInMother is inserted in motherdungeon1 at the orientation levelInMotherRef.
	 * @post	shaft is inserted in motherdungeon2 at the orientation levelOrientation.
	 */
	@Before
	public void setUp(){
		square1 = new NormalSquare();
		square2 = new NormalSquare();
		square3 = new NormalSquare();
		square4 = new NormalSquare();
		square5 = new NormalSquare();
		square6 = new NormalSquare();
		square7 = new NormalSquare();
		
		dungeon = new CompositeDungeon<AccessibleSquare>(defaultOrientation);
		dungeonMutable = new CompositeDungeon<AccessibleSquare>(motherOrientation);
		rootDungeon = new CompositeDungeon<AccessibleSquare>(rootOrientation);
		motherDungeon1 = new CompositeDungeon<AccessibleSquare>(motherOrientation);
		motherDungeon2 = new CompositeDungeon<AccessibleSquare>(motherOrientation);
		level = new Level<AccessibleSquare>(levelOrientation);
		shaft = new Shaft<AccessibleSquare>(shaftOrientation);
		shaftNextMother = new Level<AccessibleSquare>(shaftNextMotherOr);
		levelInMother = new Level<AccessibleSquare>(levelInMotherOr);
		level2 = new Level<AccessibleSquare>(defaultOrientation);
		
		//De rootDungeon heeft twee subDungeons waarvan de ene een level als 
		//subdungeon heeft en de andere een shaft.
		rootDungeon.insertSubDungeon(motherReference1, motherDungeon1);
		rootDungeon.insertSubDungeon(motherReference2, motherDungeon2);
		rootDungeon.insertSubDungeon(shaftNextMotherRef, shaftNextMother);
		motherDungeon1.insertSubDungeon(orientation1, level);
		motherDungeon1.insertSubDungeon(levelInMotherRef, levelInMother);
		motherDungeon2.insertSubDungeon(levelOrientation, shaft);
	}

	@Test
	public void initializeObject_LegalCase() {
		assertTrue(motherDungeon1.getMaxCoordinates().equals(motherOrientation));
	}
	
	@Test
	public void terminate(){
		dungeon.terminate();
		assertTrue(dungeon.getSubDungeons().isEmpty() );
	}
	
	@Test
	public void sameCoordinates(){
		assertTrue(level.sameCoordinates(new Orientation(1,1,1)));
		assertFalse(level.sameCoordinates(new Orientation(1,2,1)));
		assertTrue(rootDungeon.sameCoordinates(new Orientation(1,11,21)));
	}
	
	@Test
	public void removeSquareAt(){
		dungeonMutable.insertSubDungeon( new Orientation(1,0,0),new Level<AccessibleSquare>(new Orientation(50,50,0)));
		dungeonMutable.setSquareAt(defaultOrientation, new NormalSquare());
		dungeonMutable.removeSquareAt(defaultOrientation);
		assertFalse(dungeonMutable.hasSquareAt(defaultOrientation));
	}
	
	@Test(expected = IllegalArgumentException.class)
	public void setSquareAt_IllegalCase() throws Exception{
		rootDungeon.setSquareAt(new Orientation(0,10,0), new NormalSquare());
		shaft.setSquareAt(new Orientation(10,21,25), new NormalSquare());
		//vakje toevoegen op een plaats die in een subdungeon drie gelijke coordinaten geeft
		rootDungeon.sameCoordinates(new Orientation(1,11,21));
	}
	
	@Test 
	public void setSquareAt_LegalCases(){
		rootDungeon.setSquareAt(new Orientation(520,501,530), square1);
		rootDungeon.setSquareAt(new Orientation(521,501,530), square7);
		
		assertTrue(rootDungeon.getSquareAt(new Orientation(520,501,530)).equals(square1));
		assertTrue(rootDungeon.getSquareAt(new Orientation(521,501,530)).equals(square7));
		assertTrue(motherDungeon1.getSquares().containsValue(square1));
		assertTrue(motherDungeon1.getSquareAt(new Orientation(20,1,30)).equals(square1));
		assertTrue(motherDungeon1.getSquareAt(new Orientation(21,1,30)).equals(square7));
		
		rootDungeon.setSquareAt(new Orientation(523,506,530), square3);
		rootDungeon.setSquareAt(new Orientation(10,20,25), square4);
		
		assertTrue(rootDungeon.getSquareAt(new Orientation(523,506,530)).equals(square3));
		assertTrue(rootDungeon.getSquareAt(new Orientation(10,20,25)).equals(square4));
		assertTrue(motherDungeon2.getSquareAt(new Orientation(10,10,5)).equals(square4));
		assertTrue(level.getSquareAt(new Orientation(3,6,0)).equals(square3));
		assertTrue(shaft.getSquareAt(new Orientation(0,0,5)).equals(square4));
	
		level.setSquareAt(new Orientation(5,5,0), square5);
		shaft.setSquareAt(new Orientation(0,0,3), square6);
		
		assertTrue(rootDungeon.getSquareAt(new Orientation(525,505,530)).equals(square5));
		assertTrue(rootDungeon.getSquareAt(new Orientation(10,20,23)).equals(square6));
	}
	
	@Test
	public void getSubDungeons() {
		Map<Orientation, Dungeon<AccessibleSquare>> subDungeons = rootDungeon.getSubDungeons();
		assertEquals(subDungeons.size(), 3);
		assertEquals(subDungeons.get(motherReference1), motherDungeon1);
		assertEquals(subDungeons.get(motherReference2), motherDungeon2);
		assertEquals(subDungeons.get(shaftNextMotherRef), shaftNextMother);
	}
	
	@Test
	public void getSingularDungeons() {
		Set<SingularDungeon<AccessibleSquare>> singulars = rootDungeon.getSingularDungeons();
		assertTrue(singulars.contains(level));
		assertTrue(singulars.contains(shaft));
	}
	
	@Test(expected = IllegalArgumentException.class)
	public void getReferenceOf_IllegalCase() throws Exception{
		rootDungeon.getReferenceOf(level2);
	}
	
	@Test(expected = IllegalArgumentException.class)
	public void getReferenceOf_Null() throws Exception{
		rootDungeon.getReferenceOf(null);
	}
	
	@Test
	public void getReferenceOf_LegalCase() {
		assertEquals(rootDungeon.getReferenceOf(motherDungeon1), motherReference1);
		assertEquals(rootDungeon.getReferenceOf(motherDungeon2), motherReference2);
	}
	
	@Test(expected = IllegalArgumentException.class)
	public void getSubDungeonWithReference_IllegalCase(){
		rootDungeon.getSubDungeonWithReference(null);
	}
	
	@Test
	public void getSubDungeonWithReference_Null() {
		assertEquals(rootDungeon.getSubDungeonWithReference(defaultOrientation), null);
	}
	
	@Test
	public void getSubDungeonWithReference_LegalCase() {
		assertEquals(rootDungeon.getSubDungeonWithReference(motherReference1), motherDungeon1);
	}
	
	@Test
	public void getSubDungeonAt_reference() {
		assertEquals(rootDungeon.getSubDungeonAt(motherReference1), motherDungeon1);
	}
	
	@Test
	public void getSubDungeonAt_notReference() {
		assertEquals(rootDungeon.getSubDungeonAt(new Orientation(510, 560, 549)), motherDungeon1);
	}
	
	@Test(expected = IllegalArgumentException.class)
	public void getSubDungeonAt_null() 
	throws Exception{
		assertEquals(rootDungeon.getSubDungeonAt(null), null);
	}
	
	@Test
	public void getRefernceOfSubDungeonAt(){
		assertTrue(rootDungeon.getReferenceOfSubDungeonAt(
				new Orientation(522,501,530)).equals(motherReference1));
	}
	
	@Test
	public void getReferenceOfSingularAt() {
		assertEquals(rootDungeon.getReferenceOfSingularAt(
				new Orientation(522, 501, 530)), new Orientation(520, 500, 530));
	}
	
	@Test
	public void hasAsDirectSubDungeon(){
		assertTrue(rootDungeon.hasAsDirectSubDungeon(motherDungeon1));
		assertFalse(rootDungeon.hasAsDirectSubDungeon(shaft));
	}
	
	@Test
	public void hasAsSubDungeon(){
		assertTrue(rootDungeon.hasAsSubDungeon(motherDungeon1));
		assertTrue(rootDungeon.hasAsSubDungeon(shaft));
	}
	
	@Test
	public void hasDirectSubDungeonAt(){
		assertFalse(rootDungeon.hasDirectSubDungeonAt(new Orientation(0,1,0)));
	}
	
	@Test
	public void hasSubDungeonWithReference(){
		assertTrue(rootDungeon.hasDirectSubDungeonWithReference(motherReference1));
		assertFalse(rootDungeon.hasDirectSubDungeonWithReference(shaftOrientation));
	}
	
	@Test
	public void areValidSubDungeons_Null() {
		assertFalse(rootDungeon.areValidSubDungeons(null));
	}
	
	@Test
	public void areValidSubDungeons_nonValidDungeon() {
		HashMap<Orientation, Dungeon<AccessibleSquare>> subDungeons;
		subDungeons = new HashMap<Orientation, Dungeon<AccessibleSquare>>();
		subDungeons.put(defaultOrientation, null);
		assertFalse(motherDungeon1.areValidSubDungeons(subDungeons));
	}
	
	@Test
	public void areValidsubDungeons_true() {
		assertTrue(rootDungeon.areValidSubDungeons(motherDungeon1.getSubDungeons()));
	}
	
	@Test
	public void hasProperSubDungeons() {
		assertTrue(rootDungeon.hasProperSubDungeons());
		assertTrue(motherDungeon1.hasProperSubDungeons());
		assertTrue(motherDungeon2.hasProperSubDungeons());
		assertTrue(dungeon.hasProperSubDungeons());
		assertTrue(dungeonMutable.hasProperSubDungeons());
	}
	
	@Test
	public void canInsert_hasMother() {
		assertFalse(rootDungeon.canInsert(defaultOrientation, level));
	}
	
	@Test
	public void canInsert_usedReference() {
		assertFalse(rootDungeon.canInsert(motherReference1,level2));
	}
	
	@Test
	public void canInsert_overlap() {
		assertFalse(rootDungeon.canInsert(motherReference1.add(defaultOrientation), level2));
	}
	
	@Test
	public void removeSubDungeonAt(){
		rootDungeon.setSquareAt(new Orientation(525,505,530), square1);
		rootDungeon.setSquareAt(new Orientation(501,501,500), square7);
		rootDungeon.removeSubDungeonAt(new Orientation(501,501,502));
		assertFalse(rootDungeon.getSubDungeons().containsValue(motherDungeon1));
		assertFalse(rootDungeon.getSubDungeons().containsValue(level));
		assertFalse(rootDungeon.getSubDungeons().containsValue(levelInMother));
		assertTrue(motherDungeon1.getMotherDungeon()==null);
		assertFalse(rootDungeon.getSquares().containsValue(square1));
		assertFalse(rootDungeon.getSquares().containsValue(square7));
		
	}
	
	@Test
	public void removeSubDungeonAt_neighbourcheck(){
		new Door(true, square4, Direction.EAST);
		
		assertTrue(rootDungeon.getSubDungeonAt(new Orientation(499,550,500)).equals(shaftNextMother));
		assertTrue(rootDungeon.getSubDungeonAt(new Orientation(500,550,500)).equals(motherDungeon1));
		rootDungeon.setSquareAt(new Orientation(500,550,500), square3);
		rootDungeon.setSquareAt(new Orientation(499,550,500), square4);
		rootDungeon.removeSubDungeonAt(new Orientation(500,550,500));
		assertFalse(rootDungeon.getSquares().containsValue(square3));
		assertTrue(rootDungeon.getSquares().containsValue(square4));
		assertTrue(rootDungeon.getSquares()
				.get(new Orientation(499,550,500)).getBorderAt(Direction.EAST)
				instanceof Door);
	}
	
	@Test
	public void setSubDungeon_legalCase() {
		level2.setSquareAt(O210, square2);
		level.setSquareAt(O210, square1);
		assertTrue(square1.getBorderAt(Direction.CEILING) instanceof OpenBorder);
		motherDungeon1.setSubDungeon(orientation1plus, level2);
		assertTrue(rootDungeon.getSquareAt(orientation1plus.add(motherReference1).add(O210)) == square2);
		assertTrue(motherDungeon1.getSquareAt(orientation1plus.add(O210)) == square2);
		assertTrue(rootDungeon.getSquareAt(orientation1.add(motherReference1).add(O210)) == square1);
		assertTrue(square1.getBorderAt(Direction.CEILING) instanceof Wall);
		motherDungeon1.removeSubDungeonAt(orientation1plus);
	}
	
	@Test(expected = IllegalStateException.class)
	public void insertSubDungeon_Terminated() throws Exception{
		dungeon.terminate();
		dungeon.insertSubDungeon(new Orientation(0,0,0), level);
	}
	
	@Test(expected = IllegalArgumentException.class)
	public void insertSubDungeon_canNotInsert() throws Exception {
		rootDungeon.insertSubDungeon(motherReference1, dungeon);
	}
	
	@Test
	public void insertSubDungeon_legalCase() {
		assertFalse(square1.isTerminated());
		assertFalse(square2.isTerminated());
		level2.setSquareAt(O210, square2);
		assertFalse(square1.isTerminated());
		assertFalse(square2.isTerminated());
		level.setSquareAt(O210, square1);
		motherDungeon1.insertSubDungeon(orientation1plus, level2);
		assertTrue(motherDungeon1.getSquareAt(orientation1plus.add(O210)) == square2);
		assertTrue(rootDungeon.getSquareAt(orientation1plus.add(O210).add(motherReference1)) == square2);
		assertEquals(level2.getMotherDungeon(), motherDungeon1);
	}
	
	@Test
	public void iterator_nontEmpty() {
		
		Level<AccessibleSquare> level3 = new Level<AccessibleSquare>(new Orientation(10,30,0));
		level3.setSquareAt(O210, square3);
		level3.setSquareAt(new Orientation(3,6,0), square4);
		shaft.setSquareAt(new Orientation(0,0, 3), square5);
		shaft.setSquareAt(new Orientation(0,0, 4), square6);
		
		rootDungeon.insertSubDungeon(new Orientation(900, 50, 50), level3);
		
		Set<Square> squares = new HashSet<Square>();
		Iterator<AccessibleSquare> iterator = rootDungeon.iterator();
		while(iterator.hasNext()) {
			squares.add(iterator.next());
		}
		
		assertTrue(squares.contains(square3));
		assertTrue(squares.contains(square4));
		assertTrue(squares.contains(square5));
		assertTrue(squares.contains(square6));
		
	}
	
	@Test public void iterator_empty() {
		CompositeDungeon<Square> comp = new CompositeDungeon<Square>(rootOrientation);
		assertFalse(comp.iterator().hasNext());
		Level<Square> level3 = new Level<Square>(new Orientation(10,30,0));
		comp.insertSubDungeon(levelOrientation, level3);
		assertFalse(comp.iterator().hasNext());
	}
}
